package binding

import (
	"encoding/json"
	"reflect"
)

type bind struct {
	ptr interface{}
	err *BindError
}

// map绑定器
func Bind(ptr interface{}, m map[string]interface{}) *bind {
	object := new(bind)
	object.ptr = ptr
	object.err = new(BindError)
	object.walkStruct(reflect.TypeOf(ptr), reflect.ValueOf(ptr), m)
	return object
}

// 是否有错误
func (this *bind) Error() error {
	if this.err.msg == "" {
		return nil
	}
	return this.err
}

// 遍历结构体
// tag valid-slice-item=切片验证规则
func (this *bind) walkStruct(t reflect.Type, v reflect.Value, m map[string]interface{}) {
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		//如果是继承的结构体
		if f.Anonymous {
			this.walkStruct(f.Type, v.Field(i), m)
			continue
		}
		field := f.Tag.Get("bind")
		if field == "" {
			field = f.Name
		}
		val, _ := m[field]
		this.bind(f.Type, v.Field(i), val, field, f.Tag)
		if this.err.msg != "" {
			return
		}
	}
}

// 遍历slice
func (this *bind) walkSlice(t reflect.Type, v reflect.Value, s []interface{}, fieldName string, tag reflect.StructTag) {
	t = reflect.SliceOf(t).Elem()
	slices := reflect.MakeSlice(t, len(s), len(s))
	for i, val := range s {
		this.bind(t.Elem(), slices.Index(i), val, fieldName, tag)
		if this.err.msg != "" {
			return
		}
	}
	v.Set(slices)
}

// 数据绑定并验证
func (this *bind) bind(ft reflect.Type, fv reflect.Value, vl interface{}, fieldName string, tag reflect.StructTag) {
	if this.err.msg != "" { //有错误就不能再继续下去了(这个地方在2020-05-06载了个坑）
		return
	}
	defer func() {
		if this.err.msg != "" {
			this.err.field = fieldName
		}
	}()
	var validArgs = tag.Get("valid")
	if validItemArgs := tag.Get("valid-slice-item"); validItemArgs != "" {
		validArgs = validItemArgs
	}
	valid := newValidate(validArgs)
	//判断是否为空
	if msg, ok := valid.isRequiredAndNil(vl); !ok {
		if msg != "" {
			this.err.msg = msg
		}
		return
	}
	//如果是指针类型的，需要先给指针赋值才行
	if ft.Kind() == reflect.Ptr && fv.Kind() == reflect.Ptr {
		fv.Set(reflect.New(ft.Elem()))
	}
	if ft.Kind() == reflect.Ptr {
		ft = ft.Elem()
	}
	var checkPass = func(val interface{}) bool {
		var ok bool
		this.err.msg, ok = valid.check(val)
		return ok
	}
	if fv.CanInterface() {
		if itfs, ok := fv.Addr().Interface().(json.Unmarshaler); ok {
			data, err := json.Marshal(vl)
			if err != nil {
				this.err.msg = "提交的数据异常"
				this.err.err = err
				return
			}
			if err := itfs.UnmarshalJSON(data); err == nil {
				checkPass(itfs)
				return
			}
		}
	}
	var typeError = func(err error) bool {
		if err != nil {
			this.err.msg = "提交的数据类型错误"
			this.err.err = err
		}
		return err != nil
	}
	switch ft.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if !checkPass(vl) {
			return
		}
		val, err := ToInt64(vl)
		if typeError(err) {
			return
		}
		fv.SetInt(val)
	case reflect.Bool:
		val, err := ToBool(vl)
		if typeError(err) {
			return
		}
		if !checkPass(val) {
			return
		}
		fv.SetBool(val)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		if !checkPass(vl) {
			return
		}
		val, err := ToUint64(vl)
		if typeError(err) {
			return
		}
		fv.SetUint(val)
	case reflect.Float32, reflect.Float64:
		if !checkPass(vl) {
			return
		}
		val, err := ToFloat64(vl)
		if typeError(err) {
			return
		}
		fv.SetFloat(val)
	case reflect.String:
		val, err := ToString(vl)
		if typeError(err) {
			return
		}
		if !checkPass(val) {
			return
		}
		fv.SetString(val)
	case reflect.Slice:
		val, err := ToSlice(vl)
		if typeError(err) {
			return
		}
		if !checkPass(val) {
			return
		}
		this.walkSlice(ft, fv, val, fieldName, tag)
	case reflect.Interface:
		fv.Set(reflect.ValueOf(vl))
	case reflect.Struct:
		if ft.Name() == "Time" {
			val, err := ToTime(vl, tag.Get("time-format"))
			if typeError(err) {
				return
			}
			if !checkPass(val) {
				return
			}
			fv.Set(reflect.ValueOf(val))
		} else {
			val, err := ToMap(vl)
			if typeError(err) {
				return
			}
			if !checkPass(val) {
				return
			}
			this.walkStruct(ft, fv, val)
		}
	default:
		dv := reflect.ValueOf(vl)
		if dv.Kind() == reflect.Ptr {
			dv = dv.Elem()
		}
		if fv.Kind() == dv.Type().Kind() {
			fv.Set(dv)
		}
	}
}
